package sim.model;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Vector;

import sim.gui.VentanaPrincipal;

public class Simulador implements Runnable {

	/* Clase con las configuraciones */
	private Config config = new Config();
	private VentanaPrincipal ventana;

	/* Variables para cada columna con los valores de inicio */
	private double dias = 0;
	private Evento evento = new Evento(Evento.EVENTO_INICIO, 0, 0);
	private double rndAvRoto = 0;
	private double proxAvRoto = config.getIntervaloRoturaAvion();
	private double proxAvControl = config.getIntervaloControlAvion();
	private int cantAvRep = 0;
	private int cantAvDisponibles = 200;
	private int cantMecTrabajando = 0;
	private double rndReparacion = 0;
	private double tiempoReparacion = 0;
	private double proxFinReparacion = 0;
	private int maxCantMecTrabajando = 0;
	@SuppressWarnings("unused")
	private int cantAvionesSimulados = 0;
	private int cantAvionesReparados = 0;

	List<Double> proxFinRepAv = new ArrayList<Double>();

	/* Variables auxiliares para calculos adicionales */
	/**
	 * Esta variable se suma cada vez que en una fila hay menos cantidad de
	 * aviones que el mínimo requerido (en el ejercicio son 185 pero se puede
	 * cambiar en la clase Config).
	 */
	private int contFaltanteAviones = 0;

	/**
	 * Esta variable se suma cada vez que en una fila hay igual o más cantidad
	 * de aviones que el mínimo requerido (en el ejercicio son 185 pero se puede
	 * cambiar en la clase Config).
	 */
	private int contCorrectaAviones = 0;

	public Simulador(VentanaPrincipal ventana) {
		super();
		this.ventana = ventana;
	}

	/**
	 * Configura el intervalo de rotura de aviones en días.
	 * 
	 * @param intervalo
	 *            the intervalo to set
	 */
	public void setIntervaloRoturaAvion(double intervalo) {
		this.config.setIntervaloRoturaAvion(intervalo);
		this.proxAvRoto = intervalo;
	}

	/**
	 * Configura el intervalo de control de aviones en días.
	 * 
	 * @param intervalo
	 *            the intervalo to set
	 */
	public void setIntervaloControlAvion(double intervalo) {
		this.config.setIntervaloControlAvion(intervalo);
		this.proxAvControl = intervalo;
	}

	/**
	 * Configura la cantidad de mecanicos necesarios por cada avion que se
	 * repara o controla.
	 * 
	 * @param cantidad
	 *            La cantidad de mecanicos necesarios por cada avion que se
	 *            repara o controla.
	 */

	public void setCantMecanicosParaReparacion(int cantidad) {
		config.setCantMecanicosParaReparacion(cantidad);
	}

	/**
	 * Configura la media usada para el calculo de la reparación cuando
	 * REPARACION_EXP_NEGATIVA=true.
	 * 
	 * @return La media usada para el calculo de la reparación.
	 */

	public void setMediaReparación(double lambda) {
		config.setMediaReparación(lambda);
	}

	/**
	 * Configura los parámetros de final de la simulación. La simulación va a
	 * terminar cuando alguno de las dos condiciones se cumpla.
	 * 
	 * @param simulacionDias
	 *            Cantidad de días a simular.
	 * @param simulacionAviones
	 *            Cantidad de Aviones a simular.
	 */
	public void setSimulationEnd(double simulacionDias, int simulacionAviones) {
		config.setSimulationEnd(simulacionDias, simulacionAviones);
	}

	@Override
	public void run() {

		System.out.println("INICIO SIMULACION...");

		// Agregamos a la tabla de la venta la primera línea con el evento
		// INICIO.
		this.ventana.clearTable();
		this.proxAvRoto = config.getIntervaloRoturaAvion();
		this.rndAvRoto = config.getLastRandomRoturas();
		this.addRowToTable();
		

		// Comenzamos la simulacion...

		while (!config.simulationHasEnded(this.dias)) {

			// Calculamos el próximo evento
			this.evento = this.calcularProxEvento();

			// Modificamos el resto de las variables de acuerdo al proximo
			// evento...

			switch (this.evento.getTipoEvento()) {

			// Si el evento es "LLEGADA DE AVION ROTO"
			case Evento.EVENTO_AVION_ROTO:
				System.out.println("Nuevo Evento EVENTO_AVION_ROTO. "
						+ this.evento.getTipoEvento());
				this.dias = this.evento.getTiempoEvento();
				this.proxAvRoto = this.dias + config.getIntervaloRoturaAvion();
				this.rndAvRoto = this.config.getLastRandomRoturas();
				this.cantAvRep++;
				this.cantAvDisponibles--;
				this.cantMecTrabajando += config
						.getCantMecanicosParaReparacion();
				this.tiempoReparacion = config.getReparacion();
				this.rndReparacion = config.getLastRandom();
				this.proxFinReparacion = this.dias + this.tiempoReparacion;
				this.proxFinRepAv.add(this.proxFinReparacion);
				if (this.cantMecTrabajando > this.maxCantMecTrabajando)
					this.maxCantMecTrabajando = this.cantMecTrabajando;
				this.cantAvionesSimulados = config.addAvionSimulado();
				break;

			// Si el evento es "LLEGADA DE AVION CONTROL"
			case Evento.EVENTO_AVION_CONTROL:
				System.out.println("Nuevo Evento EVENTO_AVION_CONTROL. "
						+ this.evento.getTipoEvento());
				this.dias = this.evento.getTiempoEvento();
				this.proxAvControl = this.dias
						+ config.getIntervaloControlAvion();
				this.cantAvRep++;
				this.cantAvDisponibles--;
				this.cantMecTrabajando += config
						.getCantMecanicosParaReparacion();
				this.tiempoReparacion = config.getReparacion();
				this.rndReparacion = config.getLastRandom();
				this.proxFinReparacion = this.dias + this.tiempoReparacion;
				this.proxFinRepAv.add(this.proxFinReparacion);
				if (this.cantMecTrabajando > this.maxCantMecTrabajando)
					this.maxCantMecTrabajando = this.cantMecTrabajando;
				this.cantAvionesSimulados = config.addAvionSimulado();
				break;

			// Si el evento es "FIN REPARACION AVION"
			case Evento.EVENTO_FIN_REP_AVION:
				System.out.println("Nuevo Evento EVENTO_FIN_REP_AVION. "
						+ this.evento.getTipoEvento());
				this.dias = this.evento.getTiempoEvento();
				this.cantAvRep--;
				this.cantAvDisponibles++;
				this.cantMecTrabajando -= config
						.getCantMecanicosParaReparacion();
				this.rndReparacion = 0;
				this.proxFinReparacion = 0;
				this.tiempoReparacion = 0;

				if (Config.isLeaveFinishedPlanes()) {
					this.proxFinRepAv.set(this.evento.getAvionFinalizado() - 1,
							Double.valueOf(0.0));
				} else {
					this.proxFinRepAv
							.remove(this.evento.getAvionFinalizado() - 1);
				}
				this.cantAvionesReparados++;
				break;

			// Otro evento? ERROR!!!
			default:
				System.err.println("ERROR: Evento desconocido. "
						+ evento.getTipoEvento());
				break;
			}

			if (this.cantAvDisponibles >= Config.getCantAvionesMinima())
				this.contCorrectaAviones++;
			else
				this.contFaltanteAviones++;

			this.addRowToTable();

		}

		System.out.println("FIN SIMULACIÓN...");
	}

	/**
	 * Calcula cual el es próximo evento y lo devuelve.
	 * 
	 * @return El próximo evento.
	 */
	private Evento calcularProxEvento() {
		double minTiempo = 0;
		int evento[] = { 0, 0 };

		minTiempo = this.proxAvRoto;
		evento[0] = Evento.EVENTO_AVION_ROTO;

		if (this.proxAvControl < minTiempo) {
			minTiempo = this.proxAvControl;
			evento[0] = Evento.EVENTO_AVION_CONTROL;
		}

		double proxFin;

		int i;
		for (i = 0; i < this.proxFinRepAv.size(); i++) {
			proxFin = this.proxFinRepAv.get(i).doubleValue();
			if (Double.compare(proxFin, 0.0) == 0)
				continue;
			if (proxFin < minTiempo) {
				minTiempo = proxFin;
				evento[0] = Evento.EVENTO_FIN_REP_AVION;
				evento[1] = i + 1;
			}
		}

		return new Evento(evento[0], evento[1], minTiempo);
	}

	/**
	 * Agrega una linea a la tabla de la ventana usando los valores de los
	 * atributos de esta clase.
	 */
	private void addRowToTable() {
		Vector<Object> linea = new Vector<Object>();

		// COLUMNAS DE LA TABLA
		// Días (Clock)
		// Evento
		// Rnd Avion Roto
		// Próx avión roto
		// Próx avión control
		// Cant aviones en raparac
		// Cant aviones disponibles
		// Macanicos reparando
		// RND reparacion
		// Tiempo reparacion
		// Próx fin reparación
		// Max cant mec trab
		// prox fin rep Av n x 10

		final String dblFormat = "%,.3f";

		linea.add(String.format(dblFormat, this.dias));
		linea.add(this.evento.getDescEvento());
		linea.add(String.format(dblFormat, this.rndAvRoto));
		linea.add(String.format(dblFormat, this.proxAvRoto));
		linea.add(String.format(dblFormat, this.proxAvControl));
		linea.add(this.cantAvRep);
		linea.add(this.cantAvDisponibles);
		linea.add(this.cantMecTrabajando);
		linea.add(this.rndReparacion == 0 ? "" : String.format(dblFormat,
				this.rndReparacion));
		linea.add(this.tiempoReparacion == 0 ? "" : String.format(dblFormat,
				this.tiempoReparacion));
		linea.add(this.proxFinReparacion == 0 ? "" : String.format(dblFormat,
				this.proxFinReparacion));
		linea.add(this.maxCantMecTrabajando);
		linea.add(this.cantAvionesReparados);

		if (Config.isSortFinrepavElements())
			Collections.sort(this.proxFinRepAv);

		for (double proxFin : this.proxFinRepAv) {
			linea.add((proxFin == 0 && !Config.isSortFinrepavElements()) ? ""
					: String.format(dblFormat, proxFin));
			// linea.add(String.format(dblFormat, proxFin));
		}

		this.ventana.addRow(linea);

		double porcAvionesDisp = 0;
		if ((this.contCorrectaAviones + this.contFaltanteAviones) != 0) {
			porcAvionesDisp = ((double) this.contCorrectaAviones * 100)
					/ ((double) (this.contCorrectaAviones + this.contFaltanteAviones));
			// System.out
			// .println("Porcentaje de disponibilidad mínima de aviones: "
			// + porcAvionesDisp);
		}

		ventana.setInfoTexts(porcAvionesDisp, Config.getCantAvionesMinima(),
				this.maxCantMecTrabajando);

		this.ventana.repaint();
	}

	/**
	 * Retorna la instancia de la clase Config.
	 * 
	 * @return La configuración.
	 */
	public Config getConfig() {
		return config;
	}

}
